package com.geecity.flowable.flowabledemo.service.impl;


import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ssh.JschUtil;
import com.geecity.flowable.flowabledemo.entity.R;
import com.geecity.flowable.flowabledemo.service.FileService;

import com.geecity.flowable.flowabledemo.util.DispatchUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author yuanDing
 * @version 1.0
 * @date 2025-07-24 9:57
 */

@Slf4j
@Service
public class FileServiceImpl implements FileService {

    @Value("${dispatch.dispatchTempPath}")
    private String dispatchTempPath;

    /**
     * 获取文件夹及其子类列表
     * @return
     */
    @Override
    public R list() {
        log.info("开始获取文件夹列表");

        /**
         * 返回结果
         *  结构：
         *      - 父级文件夹（Map的Key）
         *        - 子级/后代文件/文件夹信息（Map的Value，使用Map，存储文件/文件夹名/文件的话显示文件大小/最后修改时间/ ，递归查出后代文件夹中的所有层级）
         *
         */
        ArrayList<Map<String, Object>> result = new ArrayList<>();

        /**
         * 服务器IP和文件夹路径(测试数据)
         */
        ArrayList<Map<String , Object>> folderList = new ArrayList<>();
        folderList.add(
                new HashMap<String , Object>(){{
                    put("serverIp" , "172.31.242.11");
                    put("folderPath" , "/ydTest2");
                }}
        );
        folderList.add(
                new HashMap<String , Object>(){{
                    put("serverIp" , "172.31.242.11");
                    put("folderPath" , "/ydTest");
                }}
        );
        
        log.info("配置的文件夹数量: {}", folderList.size());

        for (Map<String, Object> folderMap : folderList) {
            HashMap<String, Object> resultMap = new HashMap<>();

            String serverIp = folderMap.get("serverIp").toString();
            String folderPath = folderMap.get("folderPath").toString();
            
            log.info("处理文件夹配置 - 服务器: {}, 路径: {}", serverIp, folderPath);

            if (StrUtil.isBlankIfStr(folderPath)) {
                log.warn("文件夹路径配置为空，跳过处理");
                resultMap.put("status" , "0");
                resultMap.put("message" , "文件夹路径配置为空！");
                continue;
            }

            if (StrUtil.isBlankIfStr(serverIp)) {
                log.warn("服务器IP配置为空，跳过处理");
                resultMap.put("status" , "0");
                resultMap.put("message" , "服务器ip配置为空！");
                continue;
            }

            /**
             * 检查远程连接是否正常 服务器间已提前建立免密登录
             */
            log.info("开始检查SSH连接 - 服务器: {}", serverIp);
            boolean isConnect = DispatchUtil.checkSshConnection(serverIp);
            if (!isConnect) {
                log.error("SSH连接失败 - 服务器: {}", serverIp);
                resultMap.put("status" , "0");
                resultMap.put("message" , "服务器免密登录失败！");
                continue;
            }

            try {
                log.info("开始获取文件夹树形结构 - 服务器: {}, 路径: {}", serverIp, folderPath);
                // 获取文件夹树形结构
                Map<String, Object> folderTree = getRemoteFolderTree(serverIp, folderPath);
                
                log.info("成功获取文件夹树形结构 - 服务器: {}, 路径: {}", serverIp, folderPath);
                resultMap.put("status", "1");
                resultMap.put("message", "获取成功");
                resultMap.put("serverIp", serverIp);
                resultMap.put("folderPath", folderPath);
                resultMap.put("data", folderTree);
                
            } catch (Exception e) {
                log.error("获取文件列表失败 - 服务器: {}, 路径: {}", serverIp, folderPath, e);
                resultMap.put("status", "0");
                resultMap.put("message", "获取文件列表失败");
            }
            
            result.add(resultMap);

        }


        return R.ok(result);
    }

    @Override
    public R info(String folderPath) {
        // 使用模拟数据
        HashMap<String, Object> serverMap = new HashMap<String, Object>() {{
            put("serverIp", "172.31.242.11");
            put("folderPath", "/ydTest");
        }};

        String serverIp = (String) serverMap.get("serverIp");
        String targetPath = (String) serverMap.get("folderPath");
        
        // 如果传入了具体路径，使用传入的路径
        if (StrUtil.isNotBlank(folderPath)) {
            targetPath = folderPath;
        }

        try {
            log.info("开始获取单个文件夹完整信息 - 服务器: {}, 路径: {}", serverIp, targetPath);
            
            // 复用 list 方法中的逻辑，获取完整的文件夹树形结构
            Map<String, Object> folderTree = getRemoteFolderTree(serverIp, targetPath);
            
            log.info("成功获取单个文件夹完整信息 - 服务器: {}, 路径: {}", serverIp, targetPath);
            
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put("status", "1");
            resultMap.put("message", "获取成功");
            resultMap.put("serverIp", serverIp);
            resultMap.put("folderPath", targetPath);
            resultMap.put("data", folderTree);
            
            return R.ok(resultMap);
            
        } catch (Exception e) {
            log.error("获取单个文件夹信息失败 - 服务器: {}, 路径: {}", serverIp, targetPath, e);
            
            Map<String, Object> errorMap = new HashMap<>();
            errorMap.put("status", "0");
            errorMap.put("message", "获取文件夹信息失败: " + e.getMessage());
            errorMap.put("serverIp", serverIp);
            errorMap.put("folderPath", targetPath);
            
            return R.fail(errorMap);
        }
    }

    /**
     * 获取远程服务器文件夹树形结构
     * @param serverIp 服务器IP
     *
     * @param folderPath 文件夹路径
     * @return 树形结构数据
     */
    private Map<String, Object> getRemoteFolderTree(String serverIp, String folderPath) throws Exception {
        log.info("构建远程文件查询命令 - 服务器: {}, 路径: {}", serverIp, folderPath);
        
        // 直接使用ls -laR命令，如果目录不存在会返回错误
        String command = String.format("ls -laR %s", folderPath);
        
        log.info("执行远程查询命令: {}", command);
        String result = DispatchUtil.executeRemoteCommand(serverIp, command);
        
        if (StrUtil.isBlank(result)) {
            log.error("远程命令返回空结果 - 服务器: {}, 路径: {}", serverIp, folderPath);
            throw new RuntimeException("无法获取文件夹信息，可能路径不存在或无权限访问");
        }
        
        log.info("远程命令执行成功，开始解析结果 - 服务器: {}, 结果长度: {} 字符", serverIp, result.length());
        return parseFileListToTreeFromLsR(result, folderPath);
    }

    /**
     * 解析ls -laR命令输出为树形结构
     * @param lsROutput ls -laR命令的输出
     * @param rootPath 根路径
     * @return 树形结构
     */
    private Map<String, Object> parseFileListToTreeFromLsR(String lsROutput, String rootPath) {
        log.info("开始解析ls -laR输出为树形结构，根路径: {}", rootPath);
        log.debug("ls -laR原始输出内容:\n{}", lsROutput);
        
        Map<String, Map<String, Object>> allNodes = new HashMap<>();
        String[] sections = lsROutput.split("\n\n");
        
        log.info("ls -laR输出分段数: {}", sections.length);
        
        // 打印每个分段的内容用于调试
        for (int i = 0; i < sections.length; i++) {
            log.debug("分段 {}: \n{}", i, sections[i]);
        }
        
        int totalFileCount = 0;
        int totalDirCount = 0;
        
        for (String section : sections) {
            if (StrUtil.isBlank(section)) continue;
            
            String[] lines = section.split("\n");
            if (lines.length == 0) continue;
            
            // 第一行通常是目录路径，格式如: /path/to/dir:
            String currentDir = null;
            int startIndex = 0;
            
            if (lines[0].endsWith(":")) {
                currentDir = lines[0].substring(0, lines[0].length() - 1);
                startIndex = 1;
                log.debug("处理目录: {}", currentDir);
            } else {
                // 如果没有目录标识，可能是根目录
                currentDir = rootPath;
                log.debug("处理根目录: {}", currentDir);
            }
            
            // 跳过总计行（如果存在）
            if (startIndex < lines.length && (lines[startIndex].startsWith("总计") || lines[startIndex].startsWith("total"))) {
                startIndex++;
            }
            
            // 解析文件列表
            for (int i = startIndex; i < lines.length; i++) {
                String line = lines[i].trim();
                if (StrUtil.isBlank(line)) continue;
                
                String[] parts = line.split("\\s+");
                if (parts.length < 9) {
                    log.debug("跳过格式不正确的行: {}", line);
                    continue;
                }
                
                String permissions = parts[0];
                String size = parts[4];
                String month = parts[5];
                String day = parts[6];
                String timeOrYear = parts[7];
                String fileName = parts[8];
                
                // 跳过 . 和 .. 目录
                if (".".equals(fileName) || "..".equals(fileName)) {
                    continue;
                }
                
                // 构建完整路径
                String fullPath;
                if (currentDir.equals("/")) {
                    fullPath = "/" + fileName;
                } else if (currentDir.endsWith("/")) {
                    fullPath = currentDir + fileName;
                } else {
                    fullPath = currentDir + "/" + fileName;
                }
                
                boolean isDirectory = permissions.startsWith("d");
                if (isDirectory) {
                    totalDirCount++;
                } else {
                    totalFileCount++;
                }
                
                log.debug("解析文件/文件夹: {} ({})", fullPath, isDirectory ? "目录" : "文件");
                
                // 构建文件/文件夹信息
                Map<String, Object> nodeInfo = new HashMap<>();
                nodeInfo.put("name", fileName);
                nodeInfo.put("path", fullPath);
                nodeInfo.put("isDirectory", isDirectory);
                nodeInfo.put("type", isDirectory ? "folder" : "file");
                nodeInfo.put("size", formatFileSize(size, isDirectory));
                nodeInfo.put("lastModified", formatLastModified(month, day, timeOrYear));
                nodeInfo.put("permissions", permissions);
                nodeInfo.put("children", new ArrayList<Map<String, Object>>());
                
                allNodes.put(fullPath, nodeInfo);
            }
        }
        
        log.info("解析完成 - 总文件数: {}, 总目录数: {}, 总节点数: {}", 
            totalFileCount, totalDirCount, allNodes.size());
        
        // 确保根节点存在于allNodes中
        if (!allNodes.containsKey(rootPath)) {
            log.info("根节点不在解析结果中，手动添加根节点: {}", rootPath);
            Map<String, Object> rootNodeInfo = new HashMap<>();
            rootNodeInfo.put("name", getFileName(rootPath));
            rootNodeInfo.put("path", rootPath);
            rootNodeInfo.put("isDirectory", true);
            rootNodeInfo.put("size", "-");
            rootNodeInfo.put("lastModified", "");
            rootNodeInfo.put("type", "folder");
            rootNodeInfo.put("permissions", "drwxr-xr-x");
            rootNodeInfo.put("children", new ArrayList<Map<String, Object>>());
            allNodes.put(rootPath, rootNodeInfo);
        }
        
        // 构建树形结构
        return buildTreeStructure(allNodes, rootPath);
    }

    /**
     * 解析文件列表字符串为树形结构
     * @param fileListStr 文件列表字符串
     * @param rootPath 根路径
     * @return 树形结构
     */
    private Map<String, Object> parseFileListToTree(String fileListStr, String rootPath) {
        log.info("开始解析文件列表字符串为树形结构，根路径: {}", rootPath);
        
        Map<String, Object> rootNode = new HashMap<>();
        Map<String, Map<String, Object>> allNodes = new HashMap<>();
        
        String[] lines = fileListStr.split("\n");
        log.info("文件列表总行数: {}", lines.length);
        
        int validLineCount = 0;
        int fileCount = 0;
        int dirCount = 0;
        
        for (String line : lines) {
            if (StrUtil.isBlank(line)) continue;
            
            // 解析ls -la输出格式
            String[] parts = line.trim().split("\\s+");
            if (parts.length < 9) {
                log.debug("跳过格式不正确的行: {}", line);
                continue;
            }
            
            validLineCount++;
            String permissions = parts[0];
            String size = parts[4];
            String month = parts[5];
            String day = parts[6];
            String timeOrYear = parts[7];
            String fullPath = parts[8];
            
            boolean isDirectory = permissions.startsWith("d");
            if (isDirectory) {
                dirCount++;
            } else {
                fileCount++;
            }
            
            log.debug("解析文件/文件夹: {} ({})", fullPath, isDirectory ? "目录" : "文件");
            
            // 构建文件/文件夹信息
            Map<String, Object> nodeInfo = new HashMap<>();
            nodeInfo.put("name", getFileName(fullPath));
            nodeInfo.put("path", fullPath);
            nodeInfo.put("isDirectory", isDirectory);
            nodeInfo.put("size", isDirectory ? "-" : size);
            nodeInfo.put("lastModified", month + " " + day + " " + timeOrYear);
            nodeInfo.put("permissions", permissions);
            nodeInfo.put("children", new ArrayList<Map<String, Object>>());
            
            allNodes.put(fullPath, nodeInfo);
        }
        
        log.info("解析完成 - 有效行数: {}, 文件数: {}, 目录数: {}", validLineCount, fileCount, dirCount);
        
        // 构建树形结构
        return buildTreeStructure(allNodes, rootPath);
    }

    /**
     * 构建树形结构
     * @param allNodes 所有节点信息
     * @param rootPath 根路径
     * @return 根节点
     */
    private Map<String, Object> buildTreeStructure(Map<String, Map<String, Object>> allNodes, String rootPath) {
        log.info("开始构建树形结构，根路径: {}, 总节点数: {}", rootPath, allNodes.size());
        
        Map<String, Object> root = allNodes.get(rootPath);
        if (root == null) {
            log.warn("根节点不存在，创建默认根节点: {}", rootPath);
            root = new HashMap<>();
            root.put("name", getFileName(rootPath));
            root.put("path", rootPath);
            root.put("isDirectory", true);
            root.put("children", new ArrayList<Map<String, Object>>());
        } else {
            log.info("找到根节点: {}", rootPath);
        }
        
        int processedCount = 0;
        int orphanCount = 0;
        
        // 为每个节点找到其父节点并建立关系
        for (Map.Entry<String, Map<String, Object>> entry : allNodes.entrySet()) {
            String currentPath = entry.getKey();
            Map<String, Object> currentNode = entry.getValue();
            
            if (currentPath.equals(rootPath)) continue;
            
            String parentPath = getParentPath(currentPath);
            Map<String, Object> parentNode = allNodes.get(parentPath);
            
            if (parentNode != null) {
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> children = (List<Map<String, Object>>) parentNode.get("children");
                children.add(currentNode);
                processedCount++;
                log.debug("建立父子关系: {} -> {}", parentPath, currentPath);
            } else {
                orphanCount++;
                log.debug("找不到父节点，孤立节点: {} (父路径: {})", currentPath, parentPath);
            }
        }
        
        // 对所有节点的子节点按最后修改时间倒序排序
        sortChildrenByLastModified(allNodes);
        
        log.info("树形结构构建完成 - 处理节点数: {}, 孤立节点数: {}", processedCount, orphanCount);
        
        return root;
    }

    /**
     * 对所有节点的子节点按最后修改时间倒序排序
     * @param allNodes 所有节点信息
     */
    private void sortChildrenByLastModified(Map<String, Map<String, Object>> allNodes) {
        log.info("开始对所有节点的子节点按最后修改时间倒序排序");
        
        int sortedNodeCount = 0;
        
        for (Map<String, Object> node : allNodes.values()) {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> children = (List<Map<String, Object>>) node.get("children");
            
            if (children != null && !children.isEmpty()) {
                children.sort((child1, child2) -> {
                    String lastModified1 = (String) child1.get("lastModified");
                    String lastModified2 = (String) child2.get("lastModified");
                    
                    // 解析时间戳进行比较
                    long timestamp1 = parseLastModifiedToTimestamp(lastModified1);
                    long timestamp2 = parseLastModifiedToTimestamp(lastModified2);
                    
                    // 倒序排序（最新的在前面）
                    return Long.compare(timestamp2, timestamp1);
                });
                
                sortedNodeCount++;
                log.debug("已排序节点: {} (子节点数: {})", node.get("path"), children.size());
            }
        }
        
        log.info("排序完成 - 已排序节点数: {}", sortedNodeCount);
    }

    /**
     * 将格式化的最后修改时间转换为时间戳用于排序
     * @param lastModified 格式化的时间字符串 (如: "2025年07月01日 19:32" 或 "2025年07月01日")
     * @return 时间戳 (毫秒)
     */
    private long parseLastModifiedToTimestamp(String lastModified) {
        if (StrUtil.isBlank(lastModified)) {
            return 0L;
        }
        
        try {
            // 处理格式: "2025年07月01日 19:32" 或 "2025年07月01日"
            String dateTimeStr = lastModified.trim();
            
            // 提取年月日
            if (dateTimeStr.contains("年") && dateTimeStr.contains("月") && dateTimeStr.contains("日")) {
                String[] parts = dateTimeStr.split("[年月日\\s]+");
                if (parts.length >= 3) {
                    int year = Integer.parseInt(parts[0]);
                    int month = Integer.parseInt(parts[1]);
                    int day = Integer.parseInt(parts[2]);
                    
                    // 提取时间部分（如果存在）
                    int hour = 0;
                    int minute = 0;
                    if (parts.length > 3 && parts[3].contains(":")) {
                        String[] timeParts = parts[3].split(":");
                        if (timeParts.length >= 2) {
                            hour = Integer.parseInt(timeParts[0]);
                            minute = Integer.parseInt(timeParts[1]);
                        }
                    }
                    
                    // 构建LocalDateTime并转换为时间戳
                    LocalDateTime dateTime = LocalDateTime.of(year, month, day, hour, minute);
                    return dateTime.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
                }
            }
            
            // 如果解析失败，尝试其他格式或返回默认值
            log.debug("无法解析时间格式: {}", lastModified);
            return 0L;
            
        } catch (Exception e) {
            log.debug("解析时间戳失败: {}", lastModified, e);
            return 0L;
        }
    }

    /**
     * 格式化文件大小为MB单位
     * @param sizeStr 文件大小字符串 (字节)
     * @param isDirectory 是否为目录
     * @return 格式化后的大小字符串 (如: 1.25MB, -)
     */
    private String formatFileSize(String sizeStr, boolean isDirectory) {
        if (isDirectory) {
            return "-";
        }
        
        try {
            long sizeInBytes = Long.parseLong(sizeStr);
            
            if (sizeInBytes == 0) {
                return "0MB";
            }
            
            // 转换为MB (1MB = 1024 * 1024 bytes)
            double sizeInMB = sizeInBytes / (1024.0 * 1024.0);
            
            // 格式化显示
            if (sizeInMB < 0.01) {
                return "< 0.01MB";
            } else if (sizeInMB < 1) {
                return String.format("%.2fMB", sizeInMB);
            } else if (sizeInMB < 10) {
                return String.format("%.2fMB", sizeInMB);
            } else {
                return String.format("%.1fMB", sizeInMB);
            }
            
        } catch (NumberFormatException e) {
            log.warn("无法解析文件大小: {}", sizeStr, e);
            return sizeStr;
        }
    }

    /**
     * 格式化文件最后修改时间
     * @param month 月份 (如: Jul, 7月)
     * @param day 日期 (如: 01, 1)
     * @param timeOrYear 时间或年份 (如: 19:32 或 2024)
     * @return 格式化后的日期字符串 (如: 2025年07月01日 19:32)
     */
    private String formatLastModified(String month, String day, String timeOrYear) {
        try {
            // 当前年份
            int currentYear = LocalDateTime.now().getYear();
            
            // 月份映射
            Map<String, String> monthMap = new HashMap<>();
            monthMap.put("Jan", "01"); monthMap.put("1月", "01");
            monthMap.put("Feb", "02"); monthMap.put("2月", "02");
            monthMap.put("Mar", "03"); monthMap.put("3月", "03");
            monthMap.put("Apr", "04"); monthMap.put("4月", "04");
            monthMap.put("May", "05"); monthMap.put("5月", "05");
            monthMap.put("Jun", "06"); monthMap.put("6月", "06");
            monthMap.put("Jul", "07"); monthMap.put("7月", "07");
            monthMap.put("Aug", "08"); monthMap.put("8月", "08");
            monthMap.put("Sep", "09"); monthMap.put("9月", "09");
            monthMap.put("Oct", "10"); monthMap.put("10月", "10");
            monthMap.put("Nov", "11"); monthMap.put("11月", "11");
            monthMap.put("Dec", "12"); monthMap.put("12月", "12");
            
            // 获取格式化的月份
            String formattedMonth = monthMap.getOrDefault(month, month);
            if (formattedMonth.length() == 1) {
                formattedMonth = "0" + formattedMonth;
            }
            
            // 格式化日期
            String formattedDay = day.length() == 1 ? "0" + day : day;
            
            // 判断timeOrYear是时间还是年份
            String year;
            String time;
            if (timeOrYear.contains(":")) {
                // 是时间格式，使用当前年份
                year = String.valueOf(currentYear);
                time = timeOrYear;
            } else {
                // 是年份格式，时间设为空
                year = timeOrYear;
                time = "";
            }
            
            // 构建最终格式
            if (StrUtil.isNotBlank(time)) {
                return String.format("%s年%s月%s日 %s", year, formattedMonth, formattedDay, time);
            } else {
                return String.format("%s年%s月%s日", year, formattedMonth, formattedDay);
            }
            
        } catch (Exception e) {
            log.warn("日期格式化失败，使用原始格式: {} {} {}", month, day, timeOrYear, e);
            return month + " " + day + " " + timeOrYear;
        }
    }

    /**
     * 获取文件名
     * @param fullPath 完整路径
     * @return 文件名
     */
    private String getFileName(String fullPath) {
        if (StrUtil.isBlank(fullPath)) return "";
        int lastSlash = fullPath.lastIndexOf("/");
        return lastSlash >= 0 ? fullPath.substring(lastSlash + 1) : fullPath;
    }

    /**
     * 获取父路径
     * @param fullPath 完整路径
     * @return 父路径
     */
    private String getParentPath(String fullPath) {
        if (StrUtil.isBlank(fullPath)) return "";
        int lastSlash = fullPath.lastIndexOf("/");
        return lastSlash > 0 ? fullPath.substring(0, lastSlash) : "/";
    }

    @Override
    public void downloadFile(String serverIp, String filePath, String type , HttpServletResponse response) throws IOException {

        log.info("开始下载文件 - 服务器: {}, 文件路径: {}", serverIp, filePath);
        
        // 获取文件名
        String fileName = getFileName(filePath);
        if (StrUtil.isBlank(fileName)) {
            log.error("无效的文件路径: {}", filePath);
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.getWriter().write("无效的文件路径");
            return;
        }

        // 根据type判断是否是文件夹
        if ("folder".equals(type)) {
            // 文件夹下载：打成压缩包后下载
            handleFolderDownload(serverIp, filePath, fileName, response);
        } else {
            // 文件下载：原有逻辑
            handleFileDownload(serverIp, filePath, fileName, response);
        }
    }

    /**
     * 处理文件夹下载
     * @param serverIp 服务器IP
     * @param folderPath 文件夹路径
     * @param folderName 文件夹名称
     * @param response HTTP响应
     */
    private void handleFolderDownload(String serverIp, String folderPath, String folderName, HttpServletResponse response) throws IOException {
        log.info("开始处理文件夹下载 - 服务器: {}, 文件夹路径: {}", serverIp, folderPath);
        
        // 检查SSH连接
        if (!DispatchUtil.checkSshConnection(serverIp)) {
            log.error("无法连接到服务器: {}", serverIp);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            response.getWriter().write("无法连接到服务器");
            return;
        }
        
        // 检查文件夹是否存在
        String checkCommand = String.format("[ -d '%s' ] && echo 'EXISTS' || echo 'NOT_FOUND'", 
                folderPath.replace("'", "'\"'\"'"));
        String checkResult = DispatchUtil.executeRemoteCommand(serverIp, checkCommand);
        
        if (!"EXISTS".equals(checkResult.trim())) {
            log.error("文件夹不存在: {}", folderPath);
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.getWriter().write("文件夹不存在: " + folderPath);
            return;
        }
        
        // 生成临时压缩包文件名
        String tempZipName = folderName + "_" + System.currentTimeMillis() + ".zip";
        String tempZipPath = "/tmp/" + tempZipName;
        
        try {
            // 在远程服务器上创建压缩包
            log.info("开始在远程服务器创建压缩包: {}", tempZipPath);
            String zipCommand;
            
            // 获取父目录和文件夹名
            String parentPath = getParentPath(folderPath);
            String targetFolderName = getFileName(folderPath);
            
            if ("/".equals(parentPath)) {
                // 根目录下的文件夹，直接压缩
                zipCommand = String.format("zip -r '%s' '%s' 2>/dev/null", 
                        tempZipPath.replace("'", "'\"'\"'"),
                        folderPath.replace("'", "'\"'\"'"));
            } else {
                // 非根目录，切换到父目录后压缩指定文件夹
                zipCommand = String.format("cd '%s' && zip -r '%s' '%s' 2>/dev/null", 
                        parentPath.replace("'", "'\"'\"'"),
                        tempZipPath.replace("'", "'\"'\"'"),
                        targetFolderName.replace("'", "'\"'\"'"));
            }
            
            log.info("执行压缩命令: {}", zipCommand);
            String zipResult = DispatchUtil.executeRemoteCommand(serverIp, zipCommand);
            log.info("压缩命令执行结果: {}", zipResult);
            
            // 检查压缩包是否创建成功
            String checkZipCommand = String.format("[ -f '%s' ] && stat -c '%%s' '%s' || echo 'NOT_FOUND'", 
                    tempZipPath.replace("'", "'\"'\"'"), 
                    tempZipPath.replace("'", "'\"'\"'"));
            String zipSizeResult = DispatchUtil.executeRemoteCommand(serverIp, checkZipCommand);
            
            if ("NOT_FOUND".equals(zipSizeResult.trim())) {
                log.error("压缩包创建失败: {}", tempZipPath);
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                response.getWriter().write("压缩包创建失败");
                return;
            }
            
            long zipSize = Long.parseLong(zipSizeResult.trim());
            log.info("压缩包创建成功 - 大小: {} 字节 ({})", zipSize, formatFileSize(String.valueOf(zipSize), false));
            
            // 设置响应头
            String downloadFileName = folderName + ".zip";
            response.setContentType("application/zip");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + downloadFileName + "\"");
            response.setHeader("Accept-Ranges", "bytes");
            response.setHeader("Cache-Control", "no-cache");
            response.setContentLengthLong(zipSize);
            
            // 下载压缩包
            FileInfo zipFileInfo = new FileInfo(zipSize, System.currentTimeMillis());
            handleFullDownload(serverIp, tempZipPath, zipFileInfo, response);
            
        } finally {
            // 安全删除临时压缩包
            cleanupTempZipFile(serverIp, tempZipPath);
        }
    }
    
    /**
     * 处理文件下载
     * @param serverIp 服务器IP
     * @param filePath 文件路径
     * @param fileName 文件名
     * @param response HTTP响应
     */
    private void handleFileDownload(String serverIp, String filePath, String fileName, HttpServletResponse response) throws IOException {
        // 获取文件信息（大小和类型）
        FileInfo fileInfo = getRemoteFileInfo(serverIp, filePath);
        if (fileInfo == null) {
            log.error("文件不存在或无法访问: {}", filePath);
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.getWriter().write("文件不存在: " + filePath);
            return;
        }

        log.info("文件信息 - 大小: {} 字节 ({})", fileInfo.size, formatFileSize(String.valueOf(fileInfo.size), false));

        // 设置响应头
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
        response.setHeader("Accept-Ranges", "bytes"); // 支持断点续传
        response.setHeader("Cache-Control", "no-cache");
        response.setContentLengthLong(fileInfo.size);

        // 检查是否为Range请求（断点续传）
        String rangeHeader = null; // request.getHeader("Range"); // 暂时不实现Range
        
        if (rangeHeader != null) {
            // TODO: 实现断点续传逻辑（暂不实现）
            handleRangeDownload(serverIp, filePath, fileInfo, rangeHeader, response);
        } else {
            // 完整文件下载
            handleFullDownload(serverIp, filePath, fileInfo, response);
        }
    }
    
    /**
     * 安全删除临时压缩包文件
     * @param serverIp 服务器IP
     * @param tempZipPath 临时压缩包路径
     */
    private void cleanupTempZipFile(String serverIp, String tempZipPath) {
        try {
            log.info("开始清理临时压缩包: {}", tempZipPath);
            
            // 安全校验：确保路径在/tmp目录下且是.zip文件
            if (!tempZipPath.startsWith("/tmp/") || !tempZipPath.endsWith(".zip")) {
                log.warn("不安全的临时文件路径，跳过删除: {}", tempZipPath);
                return;
            }
            
            // 检查文件是否存在
            String checkCommand = String.format("[ -f '%s' ] && echo 'EXISTS' || echo 'NOT_FOUND'", 
                    tempZipPath.replace("'", "'\"'\"'"));
            String checkResult = DispatchUtil.executeRemoteCommand(serverIp, checkCommand);
            
            if ("EXISTS".equals(checkResult.trim())) {
                // 删除临时文件
                String deleteCommand = String.format("rm -f '%s'", tempZipPath.replace("'", "'\"'\"'"));
                String deleteResult = DispatchUtil.executeRemoteCommand(serverIp, deleteCommand);
                log.info("临时压缩包删除完成: {} - 结果: {}", tempZipPath, deleteResult);
            } else {
                log.info("临时压缩包不存在，无需删除: {}", tempZipPath);
            }
            
        } catch (Exception e) {
            log.error("清理临时压缩包失败: {}", tempZipPath, e);
        }
    }

    /**
     * 获取远程文件信息（支持文件和文件夹）
     */
    private FileInfo getRemoteFileInfo(String serverIp, String filePath) {
        // 检查SSH连接
        if (!DispatchUtil.checkSshConnection(serverIp)) {
            log.error("无法连接到服务器: {}", serverIp);
            return null;
        }

        // 获取文件或文件夹信息：存在性、大小、类型
        String command = String.format(
            "if [ -f '%s' ]; then stat -c '%%s:%%Y:file' '%s' 2>/dev/null; elif [ -d '%s' ]; then stat -c '%%s:%%Y:dir' '%s' 2>/dev/null; else echo 'NOT_FOUND'; fi",
            filePath.replace("'", "'\"'\"'"), 
            filePath.replace("'", "'\"'\"'"),
            filePath.replace("'", "'\"'\"'"), 
            filePath.replace("'", "'\"'\"'")
        );
        
        String result = DispatchUtil.executeRemoteCommand(serverIp, command);
        if ("NOT_FOUND".equals(result.trim())) {
            return null;
        }

        try {
            String[] parts = result.trim().split(":");
            if (parts.length >= 1) {
                long size = Long.parseLong(parts[0]);
                long lastModified = parts.length > 1 ? Long.parseLong(parts[1]) * 1000 : 0; // 转换为毫秒
                String type = parts.length > 2 ? parts[2] : "file";
                
                // 如果是文件夹，大小设为0（因为文件夹大小不重要，下载时会压缩）
                if ("dir".equals(type)) {
                    size = 0;
                }
                
                return new FileInfo(size, lastModified);
            }
        } catch (NumberFormatException e) {
            log.error("解析文件信息失败: {}", result, e);
        }
        
        return null;
    }

    /**
     * 处理完整文件下载
     */
    private void handleFullDownload(String serverIp, String filePath, FileInfo fileInfo, HttpServletResponse response) {
        log.info("开始完整文件下载 - 文件大小: {} 字节", fileInfo.size);
        
        // 立即刷新响应头
        try {
            response.flushBuffer();
        } catch (IOException e) {
            log.error("刷新响应头失败", e);
            return;
        }

        // 根据文件大小选择下载策略
        if (fileInfo.size > 100 * 1024 * 1024) { // 大于100MB使用分块下载
            downloadLargeFile(serverIp, filePath, fileInfo, response);
        } else {
            downloadSmallFile(serverIp, filePath, fileInfo, response);
        }
    }

    /**
     * 下载小文件（<100MB）- 使用SSH+cat命令
     */
    private void downloadSmallFile(String serverIp, String filePath, FileInfo fileInfo, HttpServletResponse response) {
        String[] sshCommand = {
            "ssh", 
            "-o", "BatchMode=yes", 
            "-o", "ConnectTimeout=30",
            "-o", "StrictHostKeyChecking=no",
            serverIp,
            "cat '" + filePath.replace("'", "'\"'\"'") + "'"
        };
        
        log.info("小文件下载 - 执行SSH+cat命令: {}", String.join(" ", sshCommand));
        executeDownloadCommand(sshCommand, fileInfo, response);
    }

    /**
     * 下载大文件（>=100MB）- 使用分块下载
     */
    private void downloadLargeFile(String serverIp, String filePath, FileInfo fileInfo, HttpServletResponse response) {
        log.info("大文件下载 - 使用SSH+dd分块传输");
        
        // 使用SSH + dd命令分块读取文件
        String command = String.format("dd if='%s' bs=1M 2>/dev/null", 
                filePath.replace("'", "'\"'\"'"));
        
        String[] sshCommand = {
            "ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=30",
            "-o", "ServerAliveInterval=60", "-o", "ServerAliveCountMax=3",
            serverIp, command
        };
        
        executeDownloadCommand(sshCommand, fileInfo, response);
    }

    /**
     * 执行下载命令的通用方法
     */
    private void executeDownloadCommand(String[] command, FileInfo fileInfo, HttpServletResponse response) {
        try {
            long startTime = System.currentTimeMillis();
            log.info("启动下载进程，命令: {}", String.join(" ", command));
            
            Process process = Runtime.getRuntime().exec(command);
            
            // 立即检查进程是否启动成功
            try {
                Thread.sleep(100); // 等待100ms让进程启动
                if (!process.isAlive()) {
                    int exitCode = process.exitValue();
                    log.error("进程启动后立即退出，退出码: {}", exitCode);
                    logProcessError(process);
                    return;
                }
            } catch (IllegalThreadStateException e) {
                // 进程仍在运行，这是正常的
                log.info("进程正常启动并运行中");
            }
            
            try (InputStream inputStream = process.getInputStream();
                 InputStream errorStream = process.getErrorStream();
                 OutputStream outputStream = response.getOutputStream()) {
                
                // 根据文件大小调整缓冲区
                int bufferSize = fileInfo.size > 10 * 1024 * 1024 ? 64 * 1024 : 8 * 1024;
                byte[] buffer = new byte[bufferSize];
                
                long totalBytes = 0;
                long lastLogTime = startTime;
                int bytesRead;
                boolean hasData = false;
                
                log.info("开始传输文件数据 - 缓冲区大小: {} KB", bufferSize / 1024);
                
                // 检查是否有错误输出
                if (errorStream.available() > 0) {
                    byte[] errorBuffer = new byte[1024];
                    int errorBytes = errorStream.read(errorBuffer);
                    if (errorBytes > 0) {
                        String errorMsg = new String(errorBuffer, 0, errorBytes);
                        log.error("进程错误输出: {}", errorMsg);
                    }
                }
                
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    if (!hasData) {
                        hasData = true;
                        log.info("开始接收数据，首次读取: {} 字节", bytesRead);
                    }
                    
                    outputStream.write(buffer, 0, bytesRead);
                    totalBytes += bytesRead;
                    
                    // 定期刷新输出流
                    if (totalBytes % (bufferSize * 10) == 0) {
                        outputStream.flush();
                    }
                    
                    // 每5秒或每10MB记录一次进度
                    long currentTime = System.currentTimeMillis();
                    if (currentTime - lastLogTime > 5000 || totalBytes % (10 * 1024 * 1024) == 0) {
                        double progress = fileInfo.size > 0 ? (double) totalBytes / fileInfo.size * 100 : 0;
                        double speed = totalBytes / ((currentTime - startTime) / 1000.0) / (1024 * 1024);
                        log.info("下载进度: {}% ({}/{} MB), 速度: {} MB/s", 
                                String.format("%.1f", progress), totalBytes / (1024 * 1024), fileInfo.size / (1024 * 1024), String.format("%.2f", speed));
                        lastLogTime = currentTime;
                    }
                }
                
                if (!hasData) {
                    log.error("没有接收到任何数据！检查命令是否正确执行");
                    // 再次检查错误输出
                    logProcessError(process);
                }
                
                outputStream.flush();
                
                int exitCode = process.waitFor();
                long endTime = System.currentTimeMillis();
                double totalTime = (endTime - startTime) / 1000.0;
                double avgSpeed = totalBytes > 0 ? totalBytes / totalTime / (1024 * 1024) : 0;
                
                if (exitCode == 0 && totalBytes > 0) {
                    log.info("文件下载完成 - 总大小: {} MB, 耗时: {}s, 平均速度: {} MB/s", 
                            totalBytes / (1024 * 1024), String.format("%.2f", totalTime), String.format("%.2f", avgSpeed));
                } else {
                    log.error("下载失败 - 退出码: {}, 传输字节: {}", exitCode, totalBytes);
                    logProcessError(process);
                }
                
            }
            
        } catch (InterruptedException e) {
            log.error("文件下载被中断", e);
            Thread.currentThread().interrupt();
        } catch (IOException e) {
            log.error("文件下载IO异常", e);
        }
    }

    /**
     * 记录进程错误信息
     */
    private void logProcessError(Process process) {
        try (InputStream errorStream = process.getErrorStream()) {
            byte[] errorBuffer = new byte[1024];
            int errorBytes = errorStream.read(errorBuffer);
            if (errorBytes > 0) {
                String errorMsg = new String(errorBuffer, 0, errorBytes);
                log.error("命令错误信息: {}", errorMsg);
            }
        } catch (IOException e) {
            log.error("读取错误信息失败", e);
        }
    }

    /**
     * 处理Range请求（断点续传）- 暂时不实现
     */
    private void handleRangeDownload(String serverIp, String filePath, FileInfo fileInfo, String rangeHeader, HttpServletResponse response) {
        // TODO: 实现断点续传逻辑
        log.warn("暂不支持断点续传，使用完整下载");
        handleFullDownload(serverIp, filePath, fileInfo, response);
    }

    /**
     * 文件信息类
     */
    private static class FileInfo {
        final long size;
        final long lastModified;
        
        FileInfo(long size, long lastModified) {
            this.size = size;
            this.lastModified = lastModified;
        }
    }


    public R upload(String id, MultipartFile file) {
//       Map<String, Object> serverMap = dispatchMapper.selectFolderMessage(id);

        // 使用模拟数据
         HashMap<String, Object> serverMap = new HashMap<String, Object>() {{
             put("serverIp", "172.31.242.11");
             put("folderPath", "/ydTest");
         }};

        // 修复逻辑错误：应该检查是否为空
        if (serverMap == null || StrUtil.isBlankIfStr(serverMap.get("serverIp")) || StrUtil.isBlankIfStr(serverMap.get("folderPath"))){
            return R.fail("文件夹信息配置不存在或缺少配置项！");
        }

        // 验证上传文件
        if (file == null || file.isEmpty()) {
            return R.fail("上传文件不能为空！");
        }

        // 服务器ip
        String serverIp = serverMap.get("serverIp").toString();

        // 父级文件夹路径
        String folderPath = serverMap.get("folderPath").toString();

        if (!DispatchUtil.checkSshConnection(serverIp)) {
            return R.fail("分发服务器连接失败！请联系管理员检查SSH远程连接配置！");
        }

        try {
            log.info("开始上传文件 - 文件名: {}, 大小: {} 字节, 目标服务器: {}, 目标路径: {}", 
                file.getOriginalFilename(), file.getSize(), serverIp, folderPath);

            // 获取原始文件名
            String originalFilename = file.getOriginalFilename();
            if (StrUtil.isBlank(originalFilename)) {
                return R.fail("文件名不能为空！");
            }

            // 构建目标文件路径
            String targetFilePath = folderPath.endsWith("/") ? 
                folderPath + originalFilename : 
                folderPath + "/" + originalFilename;

            // 检查目标目录是否存在，不存在则创建
            String checkDirCommand = String.format("[ -d '%s' ] || mkdir -p '%s'", 
                folderPath.replace("'", "'\"'\"'"), 
                folderPath.replace("'", "'\"'\"'"));
            
            log.info("检查并创建目标目录: {}", checkDirCommand);
            String checkResult = DispatchUtil.executeRemoteCommand(serverIp, checkDirCommand);
            log.debug("目录检查结果: {}", checkResult);

            // 创建本地临时文件
            String tempFileName = "upload_" + System.currentTimeMillis() + "_" + originalFilename;
            Path tempFilePath = Paths.get(dispatchTempPath, tempFileName);
            
            // 确保临时目录存在
            Path tempDir = Paths.get(dispatchTempPath);
            log.info("临时目录路径: {}", tempDir.toAbsolutePath());
            Files.createDirectories(tempDir);
            log.info("临时目录创建成功，是否存在: {}", Files.exists(tempDir));
            
            try {
                // 直接使用 MultipartFile 的输入流上传，不创建临时文件
                log.info("直接上传文件流到远程服务器: {}", targetFilePath);
                
                try {
                    // 使用 SSH 命令通过管道上传文件
                    String uploadCommand = String.format("cat > '%s'", targetFilePath.replace("'", "'\"'\"'"));
                    
                    ProcessBuilder pb = new ProcessBuilder("ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", serverIp, uploadCommand);
                    Process process = pb.start();
                    
                    // 将文件内容写入到 SSH 进程的输入流
                    try (OutputStream processInput = process.getOutputStream();
                         InputStream fileInput = file.getInputStream()) {
                        
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        long totalBytes = 0;
                        
                        while ((bytesRead = fileInput.read(buffer)) != -1) {
                            processInput.write(buffer, 0, bytesRead);
                            totalBytes += bytesRead;
                        }
                        
                        log.info("文件内容写入完成，总字节数: {}", totalBytes);
                    }
                    
                    // 等待进程完成
                    boolean finished = process.waitFor(60, java.util.concurrent.TimeUnit.SECONDS);
                    int exitValue = process.exitValue();
                    
                    if (!finished) {
                        log.error("文件上传超时");
                        return R.fail("文件上传超时！");
                    }
                    
                    if (exitValue != 0) {
                        try (BufferedReader errorReader = new BufferedReader(
                                new InputStreamReader(process.getErrorStream(), "UTF-8"))) {
                            String errorOutput = errorReader.lines().collect(java.util.stream.Collectors.joining("\n"));
                            log.error("文件上传失败 - 退出码: {}, 错误信息: {}", exitValue, errorOutput);
                            return R.fail("文件上传失败: " + errorOutput);
                        }
                    }
                    
                    log.info("文件上传成功 - 使用 SSH 管道方式");
                    
                } catch (Exception e) {
                    log.error("文件上传异常: {}", e.getMessage(), e);
                    return R.fail("文件上传失败: " + e.getMessage());
                }

                // 验证文件是否上传成功
                String verifyCommand = String.format("[ -f '%s' ] && stat -c '%%s' '%s' || echo 'NOT_FOUND'",
                    targetFilePath.replace("'", "'\"'\"'"),
                    targetFilePath.replace("'", "'\"'\"'"));
                
                String verifyResult = DispatchUtil.executeRemoteCommand(serverIp, verifyCommand);
                
                if ("NOT_FOUND".equals(verifyResult.trim())) {
                    log.error("文件上传验证失败 - 文件未找到: {}", targetFilePath);
                    return R.fail("文件上传失败，目标文件未找到！");
                }

                long uploadedSize = Long.parseLong(verifyResult.trim());
                log.info("文件上传成功 - 文件: {}, 大小: {} 字节, 目标路径: {}", 
                    originalFilename, uploadedSize, targetFilePath);

                // 构建返回结果
                Map<String, Object> result = new HashMap<>();
                result.put("fileName", originalFilename);
                result.put("filePath", targetFilePath);
                result.put("fileSize", uploadedSize);
                result.put("serverIp", serverIp);
                result.put("uploadTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

                return R.ok( result);

            } finally {
                // 清理临时文件
                try {
                    Files.deleteIfExists(tempFilePath);
                    log.debug("临时文件已清理: {}", tempFilePath);
                } catch (Exception e) {
                    log.warn("清理临时文件失败: {}", tempFilePath, e);
                }
            }

        } catch (Exception e) {
            log.error("文件上传异常 - 文件: {}, 目标服务器: {}", file.getOriginalFilename(), serverIp, e);
            return R.fail("文件上传失败: " + e.getMessage());
        }
    }



    /**
     * 获取本机某个文件夹下的文件列表
     * @param parentFolder 父级文件夹
     * @return
     */
    public R getSubListByLocal(String parentFolder) {
        log.info("开始获取本机文件夹列表 - 路径: {}", parentFolder);
        
        if (StrUtil.isBlank(parentFolder)) {
            log.warn("文件夹路径为空");
            return R.fail("文件夹路径不能为空");
        }
        
        try {
            // 使用Hutool的FileUtil检查路径是否存在
            File folder = new File(parentFolder);
            if (!folder.exists()) {
                log.error("文件夹不存在: {}", parentFolder);
                return R.fail("文件夹不存在: " + parentFolder);
            }
            
            if (!folder.isDirectory()) {
                log.error("路径不是文件夹: {}", parentFolder);
                return R.fail("路径不是文件夹: " + parentFolder);
            }
            
            // 获取文件夹下的所有文件和子文件夹
            File[] files = folder.listFiles();
            if (files == null) {
                log.warn("无法读取文件夹内容: {}", parentFolder);
                return R.fail("无法读取文件夹内容，可能没有权限");
            }
            
            List<Map<String, Object>> fileList = new ArrayList<>();
            
            for (File file : files) {
                Map<String, Object> fileInfo = new HashMap<>();
                fileInfo.put("name", file.getName());
                fileInfo.put("path", file.getAbsolutePath());
                fileInfo.put("isDirectory", file.isDirectory());
                fileInfo.put("type", file.isDirectory() ? "folder" : "file");
                fileInfo.put("size", file.isDirectory() ? "-" : formatFileSize(String.valueOf(file.length()), false));
                fileInfo.put("lastModified", formatLocalLastModified(file.lastModified()));
                fileInfo.put("permissions", getLocalFilePermissions(file));
                
                fileList.add(fileInfo);
                log.debug("添加文件/文件夹: {} ({})", file.getName(), file.isDirectory() ? "目录" : "文件");
            }
            
            // 按最后修改时间倒序排序
            fileList.sort((f1, f2) -> {
                String lastModified1 = (String) f1.get("lastModified");
                String lastModified2 = (String) f2.get("lastModified");
                long timestamp1 = parseLastModifiedToTimestamp(lastModified1);
                long timestamp2 = parseLastModifiedToTimestamp(lastModified2);
                return Long.compare(timestamp2, timestamp1);
            });
            
            Map<String, Object> result = new HashMap<>();
            result.put("parentFolder", parentFolder);
            result.put("fileCount", fileList.size());
            result.put("files", fileList);
            
            log.info("成功获取本机文件夹列表 - 路径: {}, 文件数: {}", parentFolder, fileList.size());
            return R.ok(result);
            
        } catch (Exception e) {
            log.error("获取本机文件夹列表失败 - 路径: {}", parentFolder, e);
            return R.fail("获取文件夹列表失败: " + e.getMessage());
        }
    }

    /**
     * 获取远程服务器某个文件夹下的文件列表
     * @param serverIp 服务器IP
     * @param parentFolder 父级文件夹
     * @return
     */
    public R getSubListBySSH(String serverIp, String parentFolder) {
        log.info("开始获取远程服务器文件夹列表 - 服务器: {}, 路径: {}", serverIp, parentFolder);
        
        if (StrUtil.isBlank(serverIp)) {
            log.warn("服务器IP为空");
            return R.fail("服务器IP不能为空");
        }
        
        if (StrUtil.isBlank(parentFolder)) {
            log.warn("文件夹路径为空");
            return R.fail("文件夹路径不能为空");
        }
        
        try {
            // 检查SSH连接
            if (!DispatchUtil.checkSshConnection(serverIp)) {
                log.error("SSH连接失败 - 服务器: {}", serverIp);
                return R.fail("无法连接到服务器: " + serverIp);
            }
            
            // 检查远程文件夹是否存在
            String checkCommand = String.format("[ -d '%s' ] && echo 'EXISTS' || echo 'NOT_FOUND'", 
                    parentFolder.replace("'", "'\"'\"'"));
            String checkResult = DispatchUtil.executeRemoteCommand(serverIp, checkCommand);
            
            if (!"EXISTS".equals(checkResult.trim())) {
                log.error("远程文件夹不存在: {}", parentFolder);
                return R.fail("文件夹不存在: " + parentFolder);
            }
            
            // 执行ls -la命令获取文件列表（只获取直接子项，不递归）
            String listCommand = String.format("ls -la '%s'", parentFolder.replace("'", "'\"'\"'"));
            String listResult = DispatchUtil.executeRemoteCommand(serverIp, listCommand);
            
            if (StrUtil.isBlank(listResult)) {
                log.error("远程命令返回空结果 - 服务器: {}, 路径: {}", serverIp, parentFolder);
                return R.fail("无法获取文件夹内容，可能没有权限");
            }
            
            // 解析ls -la输出
            List<Map<String, Object>> fileList = parseRemoteFileList(listResult, parentFolder);
            
            // 按最后修改时间倒序排序
            fileList.sort((f1, f2) -> {
                String lastModified1 = (String) f1.get("lastModified");
                String lastModified2 = (String) f2.get("lastModified");
                long timestamp1 = parseLastModifiedToTimestamp(lastModified1);
                long timestamp2 = parseLastModifiedToTimestamp(lastModified2);
                return Long.compare(timestamp2, timestamp1);
            });
            
            Map<String, Object> result = new HashMap<>();
            result.put("serverIp", serverIp);
            result.put("parentFolder", parentFolder);
            result.put("fileCount", fileList.size());
            result.put("files", fileList);
            
            log.info("成功获取远程服务器文件夹列表 - 服务器: {}, 路径: {}, 文件数: {}", 
                    serverIp, parentFolder, fileList.size());
            return R.ok(result);
            
        } catch (Exception e) {
            log.error("获取远程服务器文件夹列表失败 - 服务器: {}, 路径: {}", serverIp, parentFolder, e);
            return R.fail("获取远程文件夹列表失败: " + e.getMessage());
        }
    }

    /**
     * 格式化本机文件最后修改时间
     * @param lastModified 时间戳 (毫秒)
     * @return 格式化后的日期字符串
     */
    private String formatLocalLastModified(long lastModified) {
        try {
            LocalDateTime dateTime = LocalDateTime.ofInstant(
                java.time.Instant.ofEpochMilli(lastModified), 
                java.time.ZoneId.systemDefault()
            );
            
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm");
            return dateTime.format(formatter);
            
        } catch (Exception e) {
            log.warn("本机文件时间格式化失败: {}", lastModified, e);
            return new Date(lastModified).toString();
        }
    }

    /**
     * 获取本机文件权限信息
     * @param file 文件对象
     * @return 权限字符串
     */
    private String getLocalFilePermissions(File file) {
        StringBuilder permissions = new StringBuilder();
        
        // 文件类型
        permissions.append(file.isDirectory() ? "d" : "-");
        
        // 所有者权限
        permissions.append(file.canRead() ? "r" : "-");
        permissions.append(file.canWrite() ? "w" : "-");
        permissions.append(file.canExecute() ? "x" : "-");
        
        // 组权限和其他权限（Windows下无法精确获取，使用默认值）
        permissions.append("r--r--");
        
        return permissions.toString();
    }

    /**
     * 解析远程文件列表（ls -la输出）
     * @param lsOutput ls -la命令输出
     * @param parentFolder 父文件夹路径
     * @return 文件列表
     */
    private List<Map<String, Object>> parseRemoteFileList(String lsOutput, String parentFolder) {
        List<Map<String, Object>> fileList = new ArrayList<>();
        
        String[] lines = lsOutput.split("\n");
        log.info("解析远程文件列表 - 总行数: {}", lines.length);
        
        for (String line : lines) {
            if (StrUtil.isBlank(line)) continue;
            
            // 跳过总计行
            if (line.startsWith("总计") || line.startsWith("total")) {
                continue;
            }
            
            // 解析ls -la输出格式
            String[] parts = line.trim().split("\\s+");
            if (parts.length < 9) {
                log.debug("跳过格式不正确的行: {}", line);
                continue;
            }
            
            String permissions = parts[0];
            String size = parts[4];
            String month = parts[5];
            String day = parts[6];
            String timeOrYear = parts[7];
            String fileName = parts[8];
            
            // 跳过 . 和 .. 目录
            if (".".equals(fileName) || "..".equals(fileName)) {
                continue;
            }
            
            // 构建完整路径
            String fullPath = parentFolder.endsWith("/") ? 
                parentFolder + fileName : parentFolder + "/" + fileName;
            
            boolean isDirectory = permissions.startsWith("d");
            
            Map<String, Object> fileInfo = new HashMap<>();
            fileInfo.put("name", fileName);
            fileInfo.put("path", fullPath);
            fileInfo.put("isDirectory", isDirectory);
            fileInfo.put("type", isDirectory ? "folder" : "file");
            fileInfo.put("size", formatFileSize(size, isDirectory));
            fileInfo.put("lastModified", formatLastModified(month, day, timeOrYear));
            fileInfo.put("permissions", permissions);
            
            fileList.add(fileInfo);
            log.debug("解析文件/文件夹: {} ({})", fileName, isDirectory ? "目录" : "文件");
        }
        
        log.info("远程文件列表解析完成 - 文件数: {}", fileList.size());
        return fileList;
    }

}
